
from typing import Union
import cv2,os
import numpy as np
from urllib.parse import quote, urlsplit, urlunsplit
import base64
try:
    import tool.public_tool
except:
    import public_tool
# 环境依赖
# pip install 'onnx>=1.12.0,<1.18.0', 'onnxslim>=0.1.56', 'onnxruntime-gpu'
# -i https://pypi.tuna.tsinghua.edu.cn/simple
from tool.logging_tool import *
# os.chdir(os.path.dirname(__file__))
# print("执行路径:", os.getcwd())


class ColorRGB():
    # 定义常用颜色的
    # BGR
    # 红色、绿色、蓝色、黄色、青色、品红色、
    RED = (255, 0, 0) # 红色
    GREEN = (0, 255, 0) # 绿色
    BLUE = (0, 0, 255) # 蓝色
    # rgb(255 212 0)
    YELLOW = (255, 212, 0) # 黄色
    CYAN = "#00fa9a" # 青色
    MAGENTA = (255, 0, 255) # 品红色
    WHITE = (255, 255, 255) # 白色
    BLACK = (0, 0, 0) # 黑色
    GRAY = (128, 128, 128) # 灰色
    ORANGE = (0, 165, 255) # 橙色
    PURPLE = (203, 33, 233) # 紫色（非标准，但接近）
    PINK = (255, 192, 203) # 粉色（接近标准）
    BROWN = (165, 42, 42) # 棕色（接近标准）
    DARK_BLUE = (0, 0, 139) # 深蓝色
    DARK_GREEN = (0, 100, 0) # 深绿色
    DARK_RED = (139, 0, 0) # 深红色
    # 浅蓝色 rgb(0 255 255)
    LIGHT_BLUE= (0, 255, 255)
    # 浅绿色 rgb(0 255 78)
    LIGHT_GREEN= (78, 255, 0)
    # 浅红色
    LIGHT_RED= (255, 180, 180)
    def __init__(self,color_type='bgr'):
        for name, value in ColorRGB.__dict__.items():
            if not name.startswith('__') and not callable(value):
                match(color_type):
                    case 'bgr':
                        value=self.rgb_bgr(value)
                setattr(self, name, value)
        pass
    def rgb_bgr(self,value):
        if isinstance(value, tuple) and len(value) == 3:
            value = (value[2], value[1], value[0])
        if isinstance(value, str):
            # 如果是字符串，尝试转换为 BGR 格式
            try:
                value = tuple(int(value[i:i+2], 16) for i in (1, 3, 5))  # 假设字符串是 '#RRGGBB'
            except ValueError:
                pass
        return value
    def rgb_hex(self, value):
        if isinstance(value, tuple) and len(value) == 3:
            return '#{:02x}{:02x}{:02x}'.format(value[0], value[1], value[2])
        return value
# 动态定义class
    
ColorBGR=ColorRGB('bgr')
# info_print("red:",ColorBGR.BLACK)
# exit()
def resize_with_aspect_ratio(image, width=None, height=None, inter=cv2.INTER_AREA):
    # 获取原始尺寸
    (h, w) = image.shape[:2]
    r=0
    dim=0
    # 如果宽度和高度都为None，直接返回原图
    if width is None and height is None:
        return image
    
    # 如果只指定了宽度
    if width is not None and height is None and width < w:
        # 计算高度比例
        r = width / float(w)
        dim = (width, int(h * r))
    
    # 如果只指定了高度
    elif height is not None and width is None and height < h:
        # 计算宽度比例
        r = height / float(h)
        dim = (int(w * r), height)
    if r>0:
    # 执行缩放
        resized = cv2.resize(image, dim, interpolation=inter)
    else:
        resized = image
    return resized
def resize_image_file(file_path, max_width=None, max_height=None, inter=cv2.INTER_AREA,return_path=True)->Union[str, np.ndarray]:
    # 读取图像
    try:
        
        image_path= os.path.abspath(file_path)
        image_dir= os.path.dirname(image_path)
        image_file= os.path.basename(image_path)
        image_name, image_ext = os.path.splitext(image_file)
        image_resized_name = f"{image_name}_resized{image_ext}"
        image = cv2.imread(file_path)
        resized_image = resize_with_aspect_ratio(image, max_width, max_height, inter)
        resized_image_path = os.path.join(image_dir, image_resized_name)
        if image is None:
            raise ValueError(f"无法读取图像文件: {file_path}")
        # 保存缩放后的图像
        if not return_path:
            return resized_image
        cv2.imwrite(resized_image_path, resized_image)
        return resized_image_path
    except Exception as e:
        raise ValueError(f"读取图像文件时发生错误: {e}")
    return None
def encode_url_path(url):
    parts = urlsplit(url)
    # 只对 path 部分编码
    encoded_path = quote(parts.path)
    # 重新拼接完整 URL
    return urlunsplit((parts.scheme, parts.netloc, encoded_path, parts.query, parts.fragment))

def imread_from_url(url):
    # 从 URL 下载图片数据
    from urllib.request import urlopen
    image=None
    try:
        resp = urlopen(encode_url_path(url))
        image_data = np.asarray(bytearray(resp.read()), dtype="uint8")
        
        # 解码为 OpenCV 格式（BGR）
        image = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
    except Exception as e:
        raise ValueError(f"无法从 URL 读取图像: {e}")
    return image


def fileimage_to_base64(file_path):
    try:
        with open(file_path, "rb") as image_file:
            # 读取图片二进制数据
            image_data = image_file.read()
            # 转换为 Base64 编码（返回 bytes）
            base64_bytes = base64.b64encode(image_data)
            # 转换为 UTF-8 字符串（可选）
            base64_str = base64_bytes.decode("utf-8")
            return base64_str
    except:
        return None
def image_to_base64(image: np.ndarray, format: str = 'jpg') -> str:
    """
    将 OpenCV 图像转换为 Base64 编码的字符串。

    Args:
        image (np.ndarray): 输入的 OpenCV 图像。
        format (str): 图像格式，默认为 'jpg'，可选 'png'。

    Returns:
        str: Base64 编码的图像字符串。
    """
    # 将图像编码为指定格式的字节数组
    success, buffer = cv2.imencode(f'.{format}', image)
    if not success:
        raise ValueError("无法将图像编码为指定格式")
    
    # 将字节数组转换为 Base64 字符串
    base64_str = base64.b64encode(buffer).decode('utf-8')
    return base64_str
def base64_to_fileimage(base64_str, save_path):
    try:
        # 将 Base64 字符串解码为二进制数据
        image_data = base64.b64decode(base64_str)
        # 将二进制数据写入文件
        with open(save_path, "wb") as image_file:
            image_file.write(image_data)
        return save_path
    except Exception as e:
        raise ValueError(f"无法将 Base64 字符串转换为文件: {e}")
def base64_to_image(base64_str):
    try:
        # 将 Base64 字符串解码为二进制数据
        image_data = base64.b64decode(base64_str)
        # 将二进制数据转换为 NumPy 数组
        np_array = np.frombuffer(image_data, dtype=np.uint8)
        # 解码为 OpenCV 格式（BGR）
        image = cv2.imdecode(np_array, cv2.IMREAD_COLOR)
        return image
    except Exception as e:
        raise ValueError(f"无法将 Base64 字符串转换为图像: {e}")
def frame_to_base64(frame):
    # 将 OpenCV frame 转换为 JPEG 格式的内存字节数组
    _, buffer = cv2.imencode('.jpg', frame)
    # 将字节数组编码为 base64 字符串
    img_base64 = base64.b64encode(buffer).decode('utf-8')
    return img_base64

def resize_with_padding(img, width,height):
    h, w = img.shape[:2]
    th, tw = height,width
    scale = min(tw / w, th / h)
    nw, nh = int(w * scale), int(h * scale)
    img_resized = cv2.resize(img, (nw, nh), interpolation=cv2.INTER_AREA)
    new_img = np.full((th, tw, 3), 0, dtype=img.dtype)  # 黑色背景
    top = (th - nh) // 2
    left = (tw - nw) // 2
    new_img[top:top+nh, left:left+nw] = img_resized
    return new_img

def polygon_area(pts):
    """
    计算不规则多边形面积
    :param pts: 多边形顶点坐标，形状为 (N, 2)
    :return: 面积（正值）
    """
    pts = np.array(pts)
    if pts.shape[0] < 3:
        return 0.0  # 少于3个点无面积
    x = pts[:, 0]
    y = pts[:, 1]
    area = 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))
    return area
def polygon_center(pts):
    """
    计算多边形的中心点
    :param pts: 多边形顶点坐标，形状为 (N, 2)
    :return: 中心点坐标 (x, y)
    """
    pts = np.array(pts)
    if pts.shape[0] < 3:
        return (0.0, 0.0)  # 少于3个点无中心
    x = pts[:, 0]
    y = pts[:, 1]
    center_x = np.mean(x)
    center_y = np.mean(y)
    return (center_x, center_y)
def polygon_has_point(polygon, point):
    """
    检查点是否在多边形内
    :param pts: 多边形顶点坐标，形状为 (N, 2)
    :param point: 点坐标 (x, y)
    :return: bool, 点是否在多边形内
    """
    polygon = np.array(polygon)
    point = np.array(point)
    if polygon.shape[0] < 3:
        return False  # 少于3个点无多边形
    return cv2.pointPolygonTest(polygon, tuple(point), False) >= 0
def polygons_has_point(polygons, point):
    """
    检查点是否在多个多边形内
    :param polygons: 多个多边形顶点坐标列表，每个多边形为 (N, 2)
    :param point: 点坐标 (x, y)
    :return: bool, 点是否在任意多边形内
    """
    index=0
    for pts in polygons:
        if polygon_has_point(pts, point):
            return True,index
        index+=1
    return False,index
def polygon_pack_box(pts):
    """
    计算多边形的最小包围矩形
    :param pts: 多边形顶点坐标，形状为 (N, 2)
    :return: 最小包围矩形的四个顶点坐标
    """
    if not isinstance(pts, np.ndarray):
        pts = np.array(pts)
    if pts.shape[0] < 3:
        return None  # 少于3个点无包围矩形
    rect = cv2.minAreaRect(pts)
    box = cv2.boxPoints(rect)
    box = np.int0(box)  # 转换为整数坐标
    box = box.tolist()
    return box
def polygons_pack_box(polygons):
    """
    多个多边形合并成一个区域，求整体最小包围矩形
    :param polygons: [np.ndarray(点列表, dtype=np.int32), ...]
    :return: np.ndarray(4,2)  # 四个角点
    """
    # 合并所有点
    if len(polygons) == 0:
        return None
    all_points = np.concatenate(polygons, axis=0)
    rect = cv2.minAreaRect(all_points)
    box = cv2.boxPoints(rect)
    box = np.intp(box)
    # box 转化成 list 数组
    box = box.tolist()
    return box
def box_long_edge_vector(box):
    """
    提取包围盒以长边为准的方向向量
    :param box: [(x1, y1), (x2, y2), (x3, y3), (x4, y4)]
    :return: (start_point, end_point, direction_vector)
    
    # 示例
box = [[100, 50], [200, 60], [190, 160], [90, 150]]
start, end, direction = box_long_edge_vector(box)
# start/end 为长边的两个端点，direction 为归一化方向向量
    """
    box = np.array(box)
    max_len = 0
    start, end = None, None
    for i in range(4):
        p1 = box[i]
        p2 = box[(i + 1) % 4]
        vec = p2 - p1
        length = np.linalg.norm(vec)
        if length > max_len:
            max_len = length
            start, end = p1, p2
    direction = end - start
    direction = direction / (np.linalg.norm(direction) + 1e-8)  # 单位化
    return tuple(start), tuple(end), tuple(direction)

def polygons_union_mask(polygons, shape):
    """
    计算多个多边形区域的并集蒙版
    :param polygons: [np.ndarray(N,2), ...] 多个多边形
    :param shape: (height, width) 蒙版尺寸
    :return: np.ndarray 布尔蒙版，True为并集区域
    """
    mask = np.zeros(shape, dtype=np.uint8)
    for polygon in polygons:
        cv2.fillPoly(mask, [polygon.astype(np.int32)], 1)
    return mask.astype(bool)

def masks_keep_polygon(masks: np.ndarray, polygon: np.ndarray) -> np.ndarray:
    """
    批量只保留多边形内的mask，外部全部置为False
    :param masks: [N, H, W] 的布尔蒙版
    :param polygon: 多边形点，shape=[M,2]，像素坐标
    :return: 新的 [N, H, W] 布尔蒙版
    """
    h, w = masks.shape[1], masks.shape[2]
    poly_mask = np.zeros((h, w), dtype=np.uint8)
    cv2.fillPoly(poly_mask, [polygon.astype(np.int32)], 1)
    poly_mask = poly_mask.astype(bool)
    return masks & poly_mask  # 广播机制自动作用于每一张mask
def polygon_between_polygon_min_distance(polygon1, polygon2):
    """
    计算两个多边形之间的最小距离，并返回最近的两个点
    :param polygon1: 第一个多边形顶点坐标，形状为 (N, 2)
    :param polygon2: 第二个多边形顶点坐标，形状为 (M, 2)
    :return: (最小距离, 点1, 点2)
    """
    polygon1 = np.array(polygon1)
    polygon2 = np.array(polygon2)
    if polygon1.shape[0] < 1 or polygon2.shape[0] < 1:
        return float('inf'), None, None  # 无有效点
    # 计算所有点对的距离
    diff = polygon1[:, None, :] - polygon2[None, :, :]
    dists = np.linalg.norm(diff, axis=2)
    min_idx = np.unravel_index(np.argmin(dists), dists.shape)
    min_dist = dists[min_idx]
    point1 = tuple(polygon1[min_idx[0]].tolist())
    point2 = tuple(polygon2[min_idx[1]].tolist())
    return float(min_dist), point1, point2

def polygons_between_polygons_min_distance(polygons1,polygons2):
    """
    计算一组多边形中任意两两之间的最短距离及对应点
    :param polygons: [np.ndarray(点列表, dtype=np.int32), ...]
    :return: (min_dist, point1, point2, idx1, idx2)
    """
    min_dist = float('inf')
    min_pair = (None, None)
    min_idx = (None, None)
    for i in range(len(polygons1)):
        for j in range(len(polygons2)):
            dist, p1, p2 = polygon_between_polygon_min_distance(polygons1[i], polygons2[j])
            if dist < min_dist:
                min_dist = dist
                # to list
                min_pair= [p1,p2]
                # min_pair = (p1, p2)
                min_idx = [i, j]
    return min_dist, min_pair[0], min_pair[1], min_idx[0], min_idx[1]


def parse_image_data(data)->np.ndarray:
    """远程图片,本地图片,base64字符串等数据转换为 OpenCV 图像
    """
    frame=None
    try:
        if data=="":
            warn_print("图片数据为空")
        elif data and isinstance(data, str) and data.startswith("http"):
            # 如果是URL,尝试下载图片
            info_print("从URL读取图片数据:", data)
            frame = imread_from_url(data)
        elif data and os.path.exists(data):
            info_print("从本地文件读取图片数据:", data)
            frame= cv2.imread(data)
        elif data and isinstance(data, str):
            info_print("从Base64字符串读取图片数据")
            if data.startswith("data:image/"):
                # 如果是data:image格式的Base64字符串，去掉前缀
                data = data.split(",")[-1]
            frame = base64_to_image(data)
    except:
        warn_print("图片数据读取异常:", data[:50] if isinstance(data, str) else data)
        frame=None
    if frame is None:
        raise Exception("图片数据无效或不存在")
    return frame

def resize_image_maxsize(image, maxsize=2048)->np.ndarray:
    """
    按比例缩小图片，使其长宽不超过指定的最大值。

    Args:
        image (numpy.ndarray): 输入的图像。
        max_width (int): 图片允许的最大宽度。
        max_height (int): 图片允许的最大高度。

    Returns:
        numpy.ndarray: 调整尺寸后的图片。
    """
    # 获取图片的原始尺寸
    original_height, original_width = image.shape[:2]

    # 计算缩放比例，确保长宽都不超过指定的最大值
    scale = min(maxsize / original_width, maxsize / original_height, 1.0)

    # 计算新的宽度和高度
    new_width = int(original_width * scale)
    new_height = int(original_height * scale)

    # 调整图片尺寸
    resized_image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA)
    info_print(f"调整图片尺寸: 原始尺寸({image.shape[:2]}) -> 新尺寸({resized_image.shape[:2]})")
    return resized_image
def image_correct(image1,image2,replace_bg=True):
    """图片矫正

    Args:
        image1 (_type_): 原始图
        image2 (_type_): 需要转正目标图
        bgreplace (bool, optional): 是否替换使用原始图背景替换矫正后的黑色背景区域. Defaults to True.

    Raises:
        Exception: _description_

    Returns:
        _type_: _description_
    """
    # # 读取图像
    # image1 = cv2.imread('reference_image.jpg')  # 基准图
    # image2 = cv2.imread('target_image.jpg')     # 待配准图
    # image1 = cv2.imread('tmp/c1.jpg') # 基准图
    # image2 = cv2.imread('tmp/c2.jpg') # 待配准图

    # 检查图像是否成功读取
    if image1 is None or image2 is None:
        print("图像读取失败，请检查文件路径是否正确。")
    else:
        info_print("cuda gpu devices:",cv2.cuda.getCudaEnabledDeviceCount())
        if cv2.cuda.getCudaEnabledDeviceCount() > 0:
            info_print("GPU 图片校准")
            sift = cv2.cuda.SIFT_create()  # 使用 CUDA 加速的 SIFT
            # 将图像上传到 GPU
            gpu_image1 = cv2.cuda_GpuMat()
            gpu_image2 = cv2.cuda_GpuMat()
            gpu_image1.upload(image1)
            gpu_image2.upload(image2)
            
            # 检测特征点和计算描述符
            keypoints1, descriptors1 = sift.detectAndCompute(gpu_image1, None)
            keypoints2, descriptors2 = sift.detectAndCompute(gpu_image2, None)
            
            # 下载描述符到 CPU
            descriptors1 = descriptors1.download()
            descriptors2 = descriptors2.download()
        else:
            warn_print("CPU 图像校准")
            # 初始化 SIFT 特征检测器
            sift = cv2.SIFT_create()

            # 检测特征点和描述符
            keypoints1, descriptors1 = sift.detectAndCompute(image1, None)
            keypoints2, descriptors2 = sift.detectAndCompute(image2, None)

        # 使用 FLANN 匹配特征点
        flann = cv2.FlannBasedMatcher()
        # flann = cv2.BFMatcher()
        matches = flann.knnMatch(descriptors1, descriptors2, k=2)

        # 筛选匹配点
        good_matches = []
        for m, n in matches:
            if m.distance < 0.7 * n.distance:
                good_matches.append(m)
        if len(good_matches) > 4:
            # 获取匹配点的坐标
            src_pts = np.float32([keypoints1[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
            dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)

            # 计算单应性矩阵
            H, _ = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC, 5.0)

            # 进行图像配准
            height, width, _ = image1.shape
            result = cv2.warpPerspective(image2, H, (width, height))
            # 显示结果
            # cv2.imshow('source', image1)
            # cv2.imshow('target', image2)
            # cv2.imshow('result', result)
            # 填充空白部分
            # mask = np.zeros((height, width), dtype=np.uint8)
            # cv2.fillConvexPoly(mask, np.int32(dst_pts), 255)
            # mask_inv = cv2.bitwise_not(mask)
            # image1_bg = cv2.bitwise_and(image1, image1, mask=mask_inv)
            # result_fg = cv2.bitwise_and(result, result, mask=mask)
            # final_result = cv2.add(image1_bg, result_fg)
            # cv2.imshow('Final Result', final_result)
            if replace_bg:
                # 覆盖旋转后的黑色背景区域
                black_mask = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
                black_mask[black_mask > 0] = 255  # 将非黑色区域设为白色
                black_mask = cv2.bitwise_not(black_mask)  # 反转黑白，黑色区域变白
                
                # 设定一个阈值，认为低于该阈值的像素为黑色
                # gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
                # # 阈值可根据实际情况调整，比如10或20
                # _, black_mask = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY_INV)

                # 对掩码进行膨胀操作，扩大1像素，覆盖边界
                kernel = np.ones((3, 3), np.uint8)  # 3x3结构元素，膨胀1像素
                black_mask = cv2.dilate(black_mask, kernel, iterations=1)
                # 将基准图片的对应区域复制到匹配后图片的黑色背景区域
                result_image = result.copy()
                result_image[black_mask == 255] = image1[black_mask == 255]
                # cv2.imshow('result_bg', result_image)
            else:
                result_image = result
            # 等待用户按键，然后关闭所有窗口
            # cv2.waitKey(0)
            # cv2.destroyAllWindows()
        else:
            print("匹配点不足，无法计算单应性矩阵")
            raise Exception("匹配点不足，无法计算单应性矩阵")
        
        return image1,image2,result_image

def image_add_mask(image,mask,color=ColorBGR.YELLOW,alpha=0.3):
    mask_color = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    # 只保留白色区域（即mask==255），其它区域为0
    mask_color[mask == 0] = 0
    # 设置蒙版颜色（如红色），可选
    # overlay_color = (0, 0, 255)  # 红色
    mask_color[mask == 255] = color

    # 叠加透明度
    # alpha = 0.3
    result = cv2.addWeighted(image, 1, mask_color, alpha, 0)
    return result
def image_add_polygon_mask(image, polygons, color=ColorBGR.CYAN, alpha=0.3):
    """
    在图像上添加多边形蒙版
    :param image: 输入图像
    :param polygons: 多边形顶点列表，每个多边形为 [(N, 2)] 的数组
    :param color: 蒙版颜色
    :param alpha: 蒙版透明度
    :return: 添加蒙版后的图像
    """
    mask_img = np.zeros_like(image, dtype=np.uint8)
    cv2.fillPoly(mask_img, polygons, color)
    return cv2.addWeighted(image, 1, mask_img, alpha, 0)
def image_add_polygon(image,polygons, color=ColorBGR.PURPLE, thickness=1):
    """
    在图像上绘制多边形
    :param image: 输入图像
    :param polygons: 多边形顶点列表，每个多边形为 [(N, 2)] 的数组
    :param color: 多边形颜色
    :param thickness: 线条粗细
    :return: 绘制多边形后的图像
    """
    image_copy = image.copy()
    for polygon in polygons:
        cv2.polylines(image_copy, [polygon], isClosed=True, color=color, thickness=thickness)
    return image_copy
def image_add_box(image, box, color=ColorBGR.YELLOW, thickness=1):
    """
    在图像上绘制矩形框
    :param image: 输入图像
    :param box: 矩形框的四个顶点坐标 [(x1, y1), (x2, y2), (x3, y3), (x4, y4)]
    :param color: 矩形框颜色
    :param thickness: 线条粗细
    :return: 绘制矩形框后的图像
    """
    image_copy = image.copy()
    cv2.polylines(image_copy, [np.array(box)], isClosed=True, color=color, thickness=thickness)
    return image_copy
    pass

def merge_images(images,  cols=2, size=(800, 800)):
    """
    将多张图片合并成一个大图,控制每行的图片数量和每张图片的大小,高度自适应
    :param images: 图片列表
    :param rows: 行数
    :param cols: 列数
    :param size: 每张图片的大小 (width, height)
    :return: 合并后的大图
    """
    if len(images) == 0:
        return None
    # 将多张图片合并成一个大图,控制每行的图片数量和每张图片的大小,高度自适应
    rows = (len(images) + cols - 1) // cols  # 计算行数
    merged_image = np.zeros((rows * size[1], cols * size[0], 3), dtype=np.uint8)  # 创建空白大图
    for i, img in enumerate(images):
        if img is None:
            continue
        # 调整图片大小
        # 图片不等比缩放
        # cv2.resize(img, (size[0], size[1]), interpolation=cv2.INTER_AREA)
        resized_img = cv2.resize(img, (size[0], size[1]), interpolation=cv2.INTER_AREA)
        row = i // cols
        col = i % cols
        y_offset = row * size[1]
        x_offset = col * size[0]
        merged_image[y_offset:y_offset + resized_img.shape[0], x_offset:x_offset + resized_img.shape[1]] = resized_img
    # 如果图片数量不足，填充空白区域
    for i in range(len(images), rows * cols):
        row = i // cols
        col = i % cols
        y_offset = row * size[1]
        x_offset = col * size[0]
        merged_image[y_offset:y_offset + size[1], x_offset:x_offset + size[0]] = (0, 0, 0)
    
    return merged_image
# image = cv2.imread('test.jpg')
# resized = resize_with_aspect_ratio(image, width=1600)
# cv2.imwrite('test_width300.jpg', resized)